from uuid import uuid4
import json
from time import sleep
from paho.mqtt import client as mqtt_client
import traceback

from django.conf import settings
from apps.gateway.models import Gateway
from apps import redis_client

from util.exception import RightException, PubMessageException, RecvOutException, TimeOutException, GatewayException, \
    NoConnectException


class Base:
    def __init__(self, data):
        self.pct = 0
        self.sn = 0
        self.wait_num = 300
        self.try_num = 3
        self.msg_queue = []
        self.mission = data.get("mission")
        self.is_again = data.get("is_again")
        self.sid = data.get("sid")
        self.is_pub = data.get("is_pub")
        self.path = data.get("file_path")
        self.now_tid = data.get("now_tid")
        self.md5 = data.get("md5")
        self.gateway_id = str(data.get("gateway_id"))
        self.gateway = Gateway.objects.filter(gateway_id=self.gateway_id).first()
        self.client_id = str(data.get("gateway_client_id"))
        self.is_tac31 = data.get("is_tac31")
        self.status_dict = {
            1: "文件下载失败 ",
            2: "文件载入失败",
            3: "文件校验失败",
            4: "文件不匹配",
            5: "文件内容异常",
            6: "软件更新失败"
        }

    def __get_tid(self):
        redis_client.incr("tid")
        self.tid = redis_client.get("tid").decode()
        if int(self.tid) >= 9999:
            self.tid = 0
            redis_client.set("tid", str(self.tid))

    def __get_lock(self):
        string = "".join([self.gateway_id, "is_work"])
        is_work = redis_client.incr(string)
        while is_work != 1:
            is_work = redis_client.incr(string)
            redis_client.expire(string, 30 * 60)
            sleep(0.5)

    def __continue_loop(self):
        string = "".join([self.gateway_id, "start_loop"])
        try:
            is_continue = int(redis_client.get(string))
        except:
            is_continue = 0
        return is_continue

    def __start_loop(self):
        string = "".join([self.gateway_id, "start_loop"])
        redis_client.set(string, 1)

    def __end_loop(self):
        string = "".join([self.gateway_id, "start_loop"])
        redis_client.set(string, 0)

    def __get_now_tid(self):
        string = "{}{}".format(self.gateway_id, "now_tid")
        now_tid = redis_client.get(string)
        now_tid = int(now_tid)
        return now_tid

    def __realse_lock(self):
        string = "".join([self.gateway_id, "is_work"])
        redis_client.delete(string)

    def open_client(self):
        client_id = str(uuid4())
        self.client = mqtt_client.Client(client_id=client_id)
        self.client.connect(settings.BROKER, settings.PORT)

    def close_client(self):
        self.client.disconnect()

    def on_message(self, client, userdata, msg):
        self.msg_queue.append(msg)

    def struct_msg(self):
        return "base"

    def struct_pub_topic(self):
        return "base"

    def struct_sub_topic(self):
        return "base"

    def publish(self):
        msg = self.struct_msg()
        self.pub_topic = self.struct_pub_topic()
        result = self.client.publish(self.pub_topic, msg, qos=2)
        status = result[0]
        if status != 0:
            raise PubMessageException

    def sub(self):
        self.sub_topic = self.struct_sub_topic()
        self.client.subscribe(self.sub_topic)
        self.client.on_message = self.on_message
        self.client.loop_start()

    def deal_msg(self, msg):
        msg = msg.payload.decode()
        msg = json.loads(msg)
        tid = msg.get("TID")
        string = msg.get("STRING")
        resp = msg.get("RESP")
        sn = msg.get("SN")
        md5 = msg.get("MD5")
        pct = msg.get("PCT")
        status = msg.get("STATUS")
        if resp == "00":
            if string == "TIMEOUT":
                raise TimeOutException
            self.mission.pct = 100
            if md5:
                self.mission.mission_result = md5
            else:
                self.mission.mission_result = string
            if self.path:
                self.mission.file_path = self.path
            self.mission.state = 1
            self.mission.failed_reason = ""
            self.mission.save()
            raise RightException
        elif resp == "01":
            if self.sn < sn:
                self.pct = pct
                self.mission.pct = pct
                self.mission.state = 0
                self.mission.description = string
                self.mission.save()
                self.sn = sn
        elif resp == "02":
            self.mission.state = -1
            self.mission.failed_reason = self.status_dict.get(status, "")
            self.mission.save()
            raise RightException
        else:
            raise GatewayException(resp)

    def __check_connect(self):
        if not self.is_again:
            connected = int(self.gateway.state)
        else:
            connected = True
        if not connected:
            raise NoConnectException

    def __gateway_run(self):
        self.gateway.is_running = True
        self.gateway.save()

    def __gateway_exit_run(self):
        self.gateway.is_running = False
        self.gateway.save()

    def run(self):
        try:
            self.__gateway_run()
            self.open_client()
            self.__start_loop()
            self.__get_lock()
            self.__check_connect()
            self.__get_tid()
            self.publish()
            if self.is_pub:
                return
            self.sub()
            num = self.wait_num
            while True:
                sleep(1)
                if len(self.msg_queue):
                    num = self.wait_num
                    msg = self.msg_queue.pop(0)
                    self.deal_msg(msg)
                num -= 1
                is_continue = self.__continue_loop()
                if not is_continue:
                    break
                if self.is_again:
                    now_tid = self.__get_now_tid()
                    if now_tid != self.now_tid:
                        break
                if num <= 0:
                    break
            raise RecvOutException
        except RightException:
            pass
        except Exception as e:
            if hasattr(e, "msg"):
                msg = e.msg
            else:
                msg = "程序错误"
                settings.LOGGER.error(traceback.format_exc())
                settings.LOGGER.error(self.mission.id)
            if self.is_tac31:
                if isinstance(e, RecvOutException) or isinstance(e, NoConnectException):
                    self.mission.state = 0
                    self.mission.is_again = 0
                else:
                    self.mission.state = -1
                    self.mission.is_again = 1
            else:
                self.mission.state = -1
            self.mission.failed_reason = msg
            self.mission.save()
        finally:
            self.__realse_lock()
            self.__end_loop()
            self.__gateway_exit_run()
            if self.path:
                self.mission.file_path = self.path
                self.mission.save()
            try:
                self.client.loop_stop()
                self.client.disconnect()
                self.close_client()
            except Exception as e:
                settings.LOGGER.error(e)
                settings.LOGGER.error(traceback.format_exc())

